/*
Copyright 2024 New Vector Ltd.
Copyright 2015 OpenMarket Ltd

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
 */

#import "MXKContactListViewController.h"

#import "MXKSectionedContacts.h"

#import "NSBundle+MatrixKit.h"

#import "MXKSwiftHeader.h"

@interface MXKContactListViewController ()
{
    // YES -> only matrix users
    // NO -> display local contacts
    BOOL displayMatrixUsers;
    
    // screenshot of the local contacts
    NSArray* localContactsArray;
    MXKSectionedContacts* sectionedLocalContacts;
    
    // screenshot of the matrix users
    NSArray* matrixContactsArray;
    MXKSectionedContacts* sectionedMatrixContacts;
    
    // Search
    UIBarButtonItem *searchButton;
    UISearchBar     *contactsSearchBar;
    NSMutableArray  *filteredContacts;
    MXKSectionedContacts* sectionedFilteredContacts;
    BOOL             searchBarShouldEndEditing;
    BOOL             ignoreSearchRequest;
    NSString* latestSearchedPattern;
    
    NSArray* collationTitles;
    
    // mask view while processing a request
    UIActivityIndicatorView * pendingMaskSpinnerView;
}

@end

@implementation MXKContactListViewController

#pragma mark - Class methods

+ (UINib *)nib
{
    return [UINib nibWithNibName:NSStringFromClass([MXKContactListViewController class])
                          bundle:[NSBundle bundleForClass:[MXKContactListViewController class]]];
}

+ (instancetype)contactListViewController
{
    return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKContactListViewController class])
                                          bundle:[NSBundle bundleForClass:[MXKContactListViewController class]]];
}

- (void)finalizeInit
{
    [super finalizeInit];
    
    _enableBarButtonSearch = YES;
    
    // get the system collation titles
    collationTitles = [[UILocalizedIndexedCollation currentCollation] sectionTitles];
}

- (void)dealloc
{
    searchButton = nil;
}

- (void)destroy
{
    [self removePendingActionMask];
    
    [super destroy];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // Check whether the view controller has been pushed via storyboard
    if (!_contactsControls)
    {
        // Instantiate view controller objects
        [[[self class] nib] instantiateWithOwner:self options:nil];
    }
    
    // global init
    displayMatrixUsers = (0 == self.contactsControls.selectedSegmentIndex);
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
    
    if (!_contactTableViewCellClass)
    {
        // Set default table view cell class
        self.contactTableViewCellClass = [MXKContactTableCell class];
    }
    
    // Localize string
    [_contactsControls setTitle:[VectorL10n contactMxUsers] forSegmentAtIndex:0];
    [_contactsControls setTitle:[VectorL10n contactLocalContacts] forSegmentAtIndex:1];
    
    // Apply search option in navigation bar
    self.enableBarButtonSearch = _enableBarButtonSearch;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    // Restore search mechanism (if enabled)
    ignoreSearchRequest = NO;
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    
    // The user may still press search button whereas the view disappears
    ignoreSearchRequest = YES;
    
    // Leave potential search session
    if (contactsSearchBar)
    {
        [self searchBarCancelButtonClicked:contactsSearchBar];
    }
}

- (void)scrollToTop
{
    // stop any scrolling effect
    [UIView setAnimationsEnabled:NO];
    // before scrolling to the tableview top
    self.tableView.contentOffset = CGPointMake(-self.tableView.adjustedContentInset.left, -self.tableView.adjustedContentInset.top);
    [UIView setAnimationsEnabled:YES];
}

#pragma mark -

-(void)setContactTableViewCellClass:(Class)contactTableViewCellClass
{
    // Sanity check: accept only MXKContactTableCell classes or sub-classes
    NSParameterAssert([contactTableViewCellClass isSubclassOfClass:MXKContactTableCell.class]);
    
    _contactTableViewCellClass = contactTableViewCellClass;
    [self.tableView registerClass:contactTableViewCellClass forCellReuseIdentifier:[contactTableViewCellClass defaultReuseIdentifier]];
}

- (void)setEnableBarButtonSearch:(BOOL)enableBarButtonSearch
{
    _enableBarButtonSearch = enableBarButtonSearch;
    
    if (enableBarButtonSearch)
    {
        if (!searchButton)
        {
            searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
        }
        
        // Add it in right bar items
        NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
        self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:searchButton] : @[searchButton];
    }
    else
    {
        NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
        [rightBarButtonItems removeObject:searchButton];
        self.navigationItem.rightBarButtonItems = rightBarButtonItems;
    }
}

#pragma mark - Internals

- (void)updateSectionedLocalContacts:(BOOL)force
{
    [self stopActivityIndicator];
    
    MXKContactManager* sharedManager = [MXKContactManager sharedManager];
    
    if (force || !localContactsArray)
    {
        localContactsArray = sharedManager.localContacts;
        sectionedLocalContacts = [sharedManager getSectionedContacts:localContactsArray];
    }
}

- (void)updateSectionedMatrixContacts:(BOOL)force
{
    [self stopActivityIndicator];
    
    MXKContactManager* sharedManager = [MXKContactManager sharedManager];
    
    if (force || !matrixContactsArray)
    {
        matrixContactsArray = sharedManager.matrixContacts;
        sectionedMatrixContacts = [sharedManager getSectionedContacts:matrixContactsArray];
    }
}

- (BOOL)hasPendingAction
{
    return nil != pendingMaskSpinnerView;
}

- (void)addPendingActionMask
{
    // add a spinner above the tableview to avoid that the user tap on any other button
    pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5];
    pendingMaskSpinnerView.frame = self.tableView.frame;
    pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
    
    // append it
    [self.tableView.superview addSubview:pendingMaskSpinnerView];
    
    // animate it
    [pendingMaskSpinnerView startAnimating];
}

- (void)removePendingActionMask
{
    if (pendingMaskSpinnerView)
    {
        [pendingMaskSpinnerView removeFromSuperview];
        pendingMaskSpinnerView = nil;
        [self.tableView reloadData];
    }
}

#pragma mark - UITableView dataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSInteger sectionNb;
    
    // search in progress
    if (contactsSearchBar)
    {
        sectionNb = sectionedFilteredContacts.sectionedContacts.count;
        if (!sectionNb)
        {
            // Keep at least one section to display the search bar
            sectionNb = 1;
        }
    }
    else if (displayMatrixUsers)
    {
        [self updateSectionedMatrixContacts:NO];
        sectionNb = sectionedMatrixContacts.sectionedContacts.count;
        
    }
    else
    {
        [self updateSectionedLocalContacts:NO];
        sectionNb = sectionedLocalContacts.sectionedContacts.count;
    }
    
    return sectionNb;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
    
    if (section < sectionedContacts.sectionedContacts.count)
    {
        return [sectionedContacts.sectionedContacts[section] count];
    }
    return 0;
}

- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section
{
    if (contactsSearchBar)
    {
        // Hide section titles during search session
        return nil;
    }
    
    MXKSectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
    if (section < sectionedContacts.sectionTitles.count)
    {
        return (NSString*)[sectionedContacts.sectionTitles objectAtIndex:section];
    }
    
    return nil;
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
    // do not display the collation during a search
    if (contactsSearchBar)
    {
        return nil;
    }
    
    return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)aTableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    MXKSectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
    NSInteger section = [sectionedContacts.sectionTitles indexOfObject:title];
    
    // undefined title -> jump to the first valid non empty section
    if (NSNotFound == section)
    {
        NSInteger systemCollationIndex = [collationTitles indexOfObject:title];
        
        // find in the system collation
        if (NSNotFound != systemCollationIndex)
        {
            systemCollationIndex--;
            
            while ((systemCollationIndex >= 0) && (NSNotFound == section))
            {
                NSString* systemTitle = [collationTitles objectAtIndex:systemCollationIndex];
                section = [sectionedContacts.sectionTitles indexOfObject:systemTitle];
                systemCollationIndex--;
            }
        }
    }
    
    return section;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MXKContactTableCell* cell = [tableView dequeueReusableCellWithIdentifier:[_contactTableViewCellClass defaultReuseIdentifier] forIndexPath:indexPath];
    cell.thumbnailDisplayBoxType = MXKTableViewCellDisplayBoxTypeCircle;
    
    MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
    
    MXKContact* contact = nil;
    
    if (indexPath.section < sectionedContacts.sectionedContacts.count)
    {
        NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
        
        if (indexPath.row < thisSection.count)
        {
            contact = [thisSection objectAtIndex:indexPath.row];
        }
    }
    
    if (contact)
    {
        cell.contactAccessoryViewType = MXKContactTableCellAccessoryMatrixIcon;
        [cell render:contact];
        cell.delegate = self;
    }
    
    return cell;
}

#pragma mark - UITableView delegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
    
    MXKContact* contact = nil;
    
    if (indexPath.section < sectionedContacts.sectionedContacts.count)
    {
        NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
        
        if (indexPath.row < thisSection.count)
        {
            contact = [thisSection objectAtIndex:indexPath.row];
        }
    }
    
    return [((Class<MXKCellRendering>)_contactTableViewCellClass) heightForCellData:contact withMaximumWidth:tableView.frame.size.width];
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    // In case of search, the section titles are hidden and the search bar is displayed in first section header.
    if (contactsSearchBar)
    {
        if (section == 0)
        {
            return contactsSearchBar.frame.size.height;
        }
        return 0;
    }
    
    // Default section header height
    return 22;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    if (contactsSearchBar && section == 0)
    {
        return contactsSearchBar;
    }
    return nil;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
    
    MXKContact* contact = nil;
    
    if (indexPath.section < sectionedContacts.sectionedContacts.count)
    {
        NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
        
        if (indexPath.row < thisSection.count)
        {
            contact = [thisSection objectAtIndex:indexPath.row];
        }
    }
    
    if (self.delegate) {
        [self.delegate contactListViewController:self didSelectContact:contact.contactID];
    }
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
{
    // Release here resources, and restore reusable cells
    if ([cell respondsToSelector:@selector(didEndDisplay)])
    {
        [(id<MXKCellRendering>)cell didEndDisplay];
    }
}

#pragma mark - Actions

- (void)onContactsRefresh:(NSNotification *)notif
{
    if ([notif.name isEqualToString:kMXKContactManagerDidUpdateMatrixContactsNotification])
    {
        [self updateSectionedMatrixContacts:YES];
    }
    else if ([notif.name isEqualToString:kMXKContactManagerDidUpdateLocalContactsNotification])
    {
        [self updateSectionedLocalContacts:YES];
    }
    else //if ([notif.name isEqualToString:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification])
    {
        // Consider here only global notifications, ignore notifications related to a specific contact.
        if (notif.object)
        {
            return;
        }
        
        [self updateSectionedLocalContacts:YES];
    }
    
    if (contactsSearchBar)
    {
        latestSearchedPattern = nil;
        [self searchBar:contactsSearchBar textDidChange:contactsSearchBar.text];
    }
    else
    {
        [self.tableView reloadData];
    }
}

- (IBAction)onSegmentValueChange:(id)sender
{
    if (sender == self.contactsControls)
    {
        displayMatrixUsers = (0 == self.contactsControls.selectedSegmentIndex);
        
        // Leave potential search session
        if (contactsSearchBar)
        {
            [self searchBarCancelButtonClicked:contactsSearchBar];
        }
        
        [self.tableView reloadData];
    }
}

#pragma mark Search management

- (void)search:(id)sender
{
    // The user may have pressed search button whereas the view controller was disappearing
    if (ignoreSearchRequest)
    {
        return;
    }
    
    if (!contactsSearchBar)
    {
        MXKSectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
        
        // Check whether there are data in which search
        if (sectionedContacts.sectionedContacts.count > 0)
        {
            // Create search bar
            contactsSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
            contactsSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
            contactsSearchBar.showsCancelButton = YES;
            contactsSearchBar.returnKeyType = UIReturnKeyDone;
            contactsSearchBar.delegate = self;
            searchBarShouldEndEditing = NO;
            
            // init the table content
            latestSearchedPattern = @"";
            filteredContacts = [(displayMatrixUsers ? matrixContactsArray : localContactsArray) mutableCopy];
            sectionedFilteredContacts = [[MXKContactManager sharedManager] getSectionedContacts:filteredContacts];
            
            [self.tableView reloadData];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                [self->contactsSearchBar becomeFirstResponder];
            });
        }
    }
    else
    {
        [self searchBarCancelButtonClicked:contactsSearchBar];
    }
}

#pragma mark - UISearchBarDelegate

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
    searchBarShouldEndEditing = NO;
    return YES;
}

- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
    return searchBarShouldEndEditing;
}

- (NSArray*)patternsFromText:(NSString*)text
{
    NSArray* items = [text componentsSeparatedByString:@" "];
    
    if (items.count <= 1)
    {
        return items;
    }
    
    NSMutableArray* patterns = [[NSMutableArray alloc] init];
    
    for (NSString* item in items)
    {
        if (item.length > 0)
        {
            [patterns addObject:item];
        }
    }
    
    return patterns;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if ((contactsSearchBar == searchBar) && (![latestSearchedPattern isEqualToString:searchText]))
    {
        latestSearchedPattern = searchText;
        
        // contacts
        NSArray* contacts = displayMatrixUsers ? matrixContactsArray : localContactsArray;
        
        // Update filtered list
        if (searchText.length && contacts.count)
        {
            filteredContacts = [[NSMutableArray alloc] init];
            
            NSArray* patterns = [self patternsFromText:searchText];
            for(MXKContact* contact in contacts)
            {
                if ([contact matchedWithPatterns:patterns])
                {
                    [filteredContacts addObject:contact];
                }
            }
        }
        else
        {
            filteredContacts = [contacts mutableCopy];
        }
        
        sectionedFilteredContacts = [[MXKContactManager sharedManager] getSectionedContacts:filteredContacts];
        
        // Refresh display
        [self.tableView reloadData];
        [self scrollToTop];
    }
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    if (contactsSearchBar == searchBar)
    {
        // "Done" key has been pressed
        searchBarShouldEndEditing = YES;
        [contactsSearchBar resignFirstResponder];
    }
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
    if (contactsSearchBar == searchBar)
    {
        // Leave search
        searchBarShouldEndEditing = YES;
        [contactsSearchBar resignFirstResponder];
        [contactsSearchBar removeFromSuperview];
        contactsSearchBar = nil;
        filteredContacts = nil;
        sectionedFilteredContacts = nil;
        latestSearchedPattern = nil;
        [self.tableView reloadData];
        [self scrollToTop];
    }
}

#pragma mark - MXKCellRendering delegate

- (void)cell:(id<MXKCellRendering>)cell didRecognizeAction:(NSString*)actionIdentifier userInfo:(NSDictionary *)userInfo
{
    if ([actionIdentifier isEqualToString:kMXKContactCellTapOnThumbnailView])
    { 
        if (self.delegate) {
            [self.delegate contactListViewController:self didTapContactThumbnail:userInfo[kMXKContactCellContactIdKey]];
        }
    }
}

- (BOOL)cell:(id<MXKCellRendering>)cell shouldDoAction:(NSString *)actionIdentifier userInfo:(NSDictionary *)userInfo defaultValue:(BOOL)defaultValue
{
    // No such action yet on contacts
    return defaultValue;
}

@end
